using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Reflection;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace Toolbox.ArrayRenderer
{
    public partial class ArrayTreeView : TreeView, IArrayRenderer
    {
        #region Delegate

        public delegate void Parser(TreeNode n, object o, bool follow, params object[] parameters);

        private Parser rendering;

        public void SetRenderingFunction(Parser renderingFunction)
        {
            this.rendering = renderingFunction;
        }

        #endregion

        public ArrayTreeView()
            : base()
        {
            this.rendering = this.Default;
            InitializeComponent();
        }

        #region Node Drawing

        protected override void OnDrawNode(DrawTreeNodeEventArgs e)
        {
            e.DrawDefault = true;

            base.OnDrawNode(e);

            if (base.ShowLines && base.ImageList != null && e.Node.ImageIndex < 0)
            {
                // Image size

                int w = base.ImageList.ImageSize.Width;
                int h = base.ImageList.ImageSize.Height;

                // Image center

                int x = e.Node.Bounds.Left - 3 - w / 2;
                int y = (e.Node.Bounds.Top + e.Node.Bounds.Bottom) / 2;

                // Image rect

                Rectangle r = new Rectangle(x, y, 0, 0);
                r.Inflate(w / 2, h / 2);

                using (Pen p = new Pen(this.LineColor, 1))
                {
                    p.DashStyle = DashStyle.Dot;

                    e.Graphics.DrawLine(p, (x - w / 2), y, (x + w / 2), y);

                    if (!this.CheckBoxes && e.Node.IsExpanded)
                    {
                        e.Graphics.DrawLine(p, x, y, x, (y + w / 2));
                    }
                }
            }
        }

        protected override void OnAfterCollapse(TreeViewEventArgs e)
        {
            base.OnAfterCollapse(e);

            if (!base.CheckBoxes && base.ImageList != null && e.Node.ImageIndex < 0)
            {
                base.Invalidate(e.Node.Bounds);
            }
        }


        #endregion

        #region IArrayRenderer Members

        public event ProgressChangedEventHandler ProgressChangedEvent;

        public void ReportProgress(int percentProgress)
        {
            this.BackgroundRenderer.ReportProgress(percentProgress);
        }

        public bool IsRendering
        {
            get { return this.BackgroundRenderer.IsBusy; }
        }

        private bool root = false;

        [Description("Show object as root element")]
        [Category("Behavior")]
        [DefaultValue(false)]
        public bool ShowRootElement
        {
            get { return this.root; }
            set { this.root = value; }
        }

        private bool follow = false;

        [Description("Scan arrays in arrays")]
        public bool FullScan
        {
            get { return this.follow; }
            set { this.follow = value; }
        }

        [Description("ImageList with type names as key")]
        public new ImageList ImageList
        {
            get { return base.ImageList; }
            set { base.ImageList = value; }
        }

        public void SetObject(IEnumerable o)
        {
            if (!this.BackgroundRenderer.IsBusy)
            {
                this.OnRendering();
                this.BackgroundRenderer.RunWorkerAsync(o);
            }
        }

        public void Cancel()
        {
            this.BackgroundRenderer.CancelAsync();
        }

        #endregion

        #region Render

        private void BackgroundRenderer_DoWork(object sender, DoWorkEventArgs e)
        {
            if (e.Argument is IEnumerable)
            {
                IEnumerable array = this.root ? new object[] { e.Argument } : e.Argument as IEnumerable;

                e.Result = new TreeNode();

                foreach (object o in array)
                {
                    this.rendering((e.Result as TreeNode).Nodes.Add(o.ToString()), o, true);
                }
            }
        }

        private void BackgroundRenderer_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (this.ProgressChangedEvent != null)
            {
                this.ProgressChangedEvent(this, e);
            }
        }

        private void BackgroundRenderer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.Nodes.Clear();
            this.Nodes.AddRange((e.Result as TreeNode).Nodes.Find("", true));
            this.OnRendered();
        }

        #endregion

        #region Events

        public event EventHandler Rendering;
        public event EventHandler Rendered;

        protected virtual void OnRendering()
        {
            this.BeginUpdate();

            this.UseWaitCursor = true;

            if (this.Rendering != null)
            {
                this.Rendering(this, new EventArgs());
            }
        }

        protected virtual void OnRendered()
        {
            this.EndUpdate();

            this.UseWaitCursor = false;

            if (this.Rendered != null)
            {
                this.Rendered(this, new EventArgs());
            }

            this.Refresh();
        }

        #endregion

        #region Default Renderer

        private void Default(TreeNode n, object o, bool follow, params object[] parameters)
        {
            ParseProperties(n, o, follow);

            if (o is IEnumerable && follow)
            {
                ParseObject(this.Default, n, o, follow, parameters);
            }
        }

        #endregion

        #region Node Selection

        public void Select(object o)
        {
            if (this.SelectedNode is TreeNode)
            {
                this.SelectedNode.Collapse();
            }

            this.BeginUpdate();

            TreeNode n = FindNodeByTag(o, this.Nodes, true);

            if (n is TreeNode)
            {
                n.EnsureVisible();
                n.Expand();
            }

            this.EndUpdate();
        }

        private static TreeNode FindNodeByTag(object o, TreeNodeCollection c, bool expand)
        {
            foreach (TreeNode n in c)
            {
                if (n.Tag == o)
                {
                    return n;
                }

                TreeNode f = FindNodeByTag(o, n.Nodes, expand);
                if (f is TreeNode)
                {
                    f.Expand();
                    return f;
                }
            }

            return null;
        }

        #endregion

        #region Edition

        protected override void OnNodeMouseDoubleClick(TreeNodeMouseClickEventArgs e)
        {
            base.OnNodeMouseDoubleClick(e);

            if (!e.Node.IsExpanded)
            {
                if (e.Node.Tag is IEnumerable)
                {
                    this.OnBeforeExpand(new TreeViewCancelEventArgs(e.Node, false, TreeViewAction.ByMouse));
                }
                else if (e.Node.Tag is object)
                {
                    this.ParseProperties(e.Node, e.Node.Tag, false);
                }
            }

            e.Node.Expand();
        }

        protected override void OnNodeMouseClick(TreeNodeMouseClickEventArgs e)
        {
            base.OnNodeMouseClick(e);

            if (e.Button == MouseButtons.Right && e.Node.Parent != null)
            {
                if (e.Node.ContextMenuStrip == null)
                {
                    e.Node.ContextMenuStrip = new ContextMenuStrip();
                }

                if (!e.Node.ContextMenuStrip.Items.ContainsKey("Edit"))
                {
                    // Add Property Info
                    PropertyInfo pi = e.Node.Parent.Tag.GetType().GetProperty(e.Node.Name);
                    if (pi is PropertyInfo && pi.CanWrite && pi.PropertyType.IsSerializable)
                    {
                        // Create Menu entry
                        ToolStripMenuItem edit = new ToolStripMenuItem("&Edit");
                        {
                            edit.Name = "Edit";
                            edit.Tag = e.Node;
                        }
                        e.Node.ContextMenuStrip.Items.Add(edit);

                        // Create SubMenu entries
                        if (pi.PropertyType.IsEnum)
                        {
                            foreach (FieldInfo f in pi.PropertyType.GetFields())
                            {
                                if (!f.IsSpecialName)
                                {
                                    ToolStripMenuItem t = new ToolStripMenuItem(f.Name);
                                    {
                                        t.Checked = f.Name.Equals(e.Node.Tag.ToString());
                                    }
                                    edit.DropDownItems.Add(t);
                                }
                            }

                            edit.DropDownItemClicked += new ToolStripItemClickedEventHandler(edit_DropDownItemClicked);
                        }
                        else
                        {
                            edit.Click += new EventHandler(edit_Click);
                        }
                    }
                }

                if (e.Node.ContextMenuStrip.Items.ContainsKey("Edit"))
                {
                    e.Node.ContextMenuStrip.Items["Edit"].Enabled = this.LabelEdit;
                }
            }

        }

        void edit_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            TreeNode n = (sender as ToolStripMenuItem).Tag as TreeNode;

            if (n is TreeNode)
            {
                this.OnAfterLabelEdit(new NodeLabelEditEventArgs(n, e.ClickedItem.Text));
            }
        }

        void edit_Click(object sender, EventArgs e)
        {
            TreeNode n = (sender as ToolStripMenuItem).Tag as TreeNode;

            if (n is TreeNode && n.Parent.Tag is object)
            {
                // Add Property Info
                PropertyInfo pi = n.Parent.Tag.GetType().GetProperty(n.Name);
                if (pi is PropertyInfo && pi.CanWrite && pi.PropertyType.IsSerializable)
                {
                    object o = pi.GetValue(n.Parent.Tag, null);

                    n.Text = o is object ? o.ToString() : "";
                    n.BeginEdit();
                }
            }
        }

        protected override void OnAfterLabelEdit(NodeLabelEditEventArgs e)
        {
            if (e.Node.Parent.Tag is object && !e.CancelEdit)
            {
                // Add Property Info
                PropertyInfo pi = e.Node.Parent.Tag.GetType().GetProperty(e.Node.Name);
                if (pi is PropertyInfo && pi.CanWrite && pi.PropertyType.IsSerializable)
                {
                    try
                    {
                        if (pi.PropertyType == typeof(String))
                        {
                            e.Node.Tag = e.Label;
                        }

                        if (pi.PropertyType == typeof(DateTime))
                        {
                            e.Node.Tag = DateTime.Parse(e.Label);
                        }

                        if (pi.PropertyType.IsEnum)
                        {
                            e.Node.Tag = Enum.Parse(pi.PropertyType, e.Label);
                        }

                        if (pi.PropertyType.IsByRef || e.Node.Tag != null)
                        {
                            pi.SetValue(e.Node.Parent.Tag, e.Node.Tag, null);
                        }
                    }
                    catch (Exception ex)
                    {
                        e.Node.ToolTipText = ex.Message;
                    }
                }
            }

            this.SetLabel(e.Node);
            base.OnAfterLabelEdit(e);
        }

        #endregion

        #region Helpers

        public void ParseObject(Parser f, TreeNode n, object o, bool follow, params object[] parameters)
        {
            if (o is IEnumerable && o.GetType().IsArray)
            {
                foreach (object e in o as IEnumerable)
                {
                    string key = e.GetHashCode().ToString();
                    TreeNode te = n.Nodes[key];
                    if (te == null)
                    {
                        te = n.Nodes.Add(key, e.ToString());
                    }

                    if (f != null)
                    {
                        f(te, e, follow, parameters);
                    }
                }
            }
        }

        public void ParseProperties(TreeNode n, object o, bool follow)
        {
            n.Tag = o;
            //this.SetLabel(n);
            this.SetIcon(n);

            foreach (PropertyInfo pi in o.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.GetProperty))
            {
                TreeNode to = n.Nodes[pi.Name];
                if (to == null)
                {
                    to = n.Nodes.Add(pi.Name, pi.Name);
                }

                to.Name = pi.Name;
                to.Tag = pi.GetValue(o, null);

                this.SetLabel(to);
                this.SetIcon(to);

                if (pi.PropertyType.IsArray && follow)
                {
                    this.ParseObject(Default, to, to.Tag, follow);
                }
            }
        }

        public void SetIcon(TreeNode n)
        {
            if (this.ImageList is object)
            {
                if (n.Tag is object && this.ImageList.Images.ContainsKey(n.Tag.GetType().FullName))
                {
                    n.ImageKey = n.Tag.GetType().FullName;
                }

                if (this.ImageList.Images.ContainsKey("@" + n.ImageKey))
                {
                    n.SelectedImageKey = "@" + n.ImageKey;
                }
                else
                {
                    n.SelectedImageKey = n.ImageKey;
                }
            }
        }

        public void SetLabel(TreeNode n)
        {
            int i;

            if (n.Tag == null || n.Tag.ToString().Trim().Length == 0)
            {
                n.Text = n.Name + ": (none)";
            }
            else if (n.Tag is ICollection)
            {
                n.ToolTipText = "Double clic to expand";
                n.Text = n.Name + " [" + (n.Tag as ICollection).Count + "]";
            }
            else if (n.Tag is Array)
            {
                n.ToolTipText = "Double clic to expand";
                n.Text = n.Name + " [" + (n.Tag as Array).Length + "]";
            }
            else if (n.Name.Trim().Length == 0 || int.TryParse(n.Name, out i))
            {
                n.Text = n.Tag.ToString();
            }
            else
            {
                n.Text = n.Name + ": " + n.Tag.ToString();
            }

            n.Collapse();
        }

        #endregion
    }
}
